#include "Terrain.h"
#include "Game.h"
#include "RandomNumberGenerator.h"
#include <d3dx9math.h>

void Terrain::Create(int size,float cellsize,D3DXVECTOR3 pos)
{
	m_bTile=false;
	m_Type="Terrain";
	HRESULT hr;
	
	m_vPos = pos;
	m_Size=size;
	m_CellSize=cellsize;

	D3DXVECTOR3 minBounds;
	D3DXVECTOR3 maxBounds;

	
	int numCellsWide = m_Size;
	int numCellsHigh = m_Size;
	m_NumCells = m_Size*m_Size;
	m_NumTris = m_NumCells*2;
	m_vTriNorms.resize(m_NumTris);

	int numVertsX=numCellsWide+1;
	int numVertsZ=numCellsHigh+1;
	m_NumVerts=numVertsX*numVertsZ;

	m_Fractal.Generate(numVertsX,(size/4)*cellsize);

	minBounds = m_vPos - D3DXVECTOR3((numCellsWide/2)*m_CellSize,0,(numCellsHigh/2)*m_CellSize);
	maxBounds = m_vPos + D3DXVECTOR3((numCellsWide/2)*m_CellSize,0,(numCellsHigh/2)*m_CellSize);

	float stepX=(maxBounds.x-minBounds.x)/numCellsWide;
	float stepZ=(maxBounds.z-minBounds.z)/numCellsHigh;
	
	V(D3DXCreateMesh(m_NumTris,m_NumVerts,D3DXMESH_MANAGED,TheD3DApp::Instance()->GetVertexElements(),TheD3DApp::Instance()->GetD3DDev(),&m_pMesh));


	//TheD3DApp::Instance()->GetD3DDev()->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW);
	

	//Index buffer stuff
	TheD3DApp::Instance()->GetD3DDev()->CreateIndexBuffer(m_NumTris*3*sizeof(WORD),
		D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_Ib, NULL);
	
	WORD *IndexData=NULL;
	V(m_pMesh->LockIndexBuffer( 0, (void**)&IndexData ));

	int indCount=0;
	int vIndex=0;
	int TriCount=0;
	for (int z=0;z<numCellsHigh;z++)
	{
		for (int x=0;x<numCellsWide;x++)
		{
			// first triangle
			int a = IndexData[indCount++]=vIndex;
			int b =IndexData[indCount++]=vIndex+1;
			int c =IndexData[indCount++]=vIndex+numVertsX+1;

			// second triangle
			IndexData[indCount++]=vIndex;
			IndexData[indCount++]=vIndex+numVertsX+1;
			int d =IndexData[indCount++]=vIndex+numVertsX;
			

			//normal stuff
			D3DXVECTOR3 apos = m_Fractal.GetPos(a);
			D3DXVECTOR3 bpos = m_Fractal.GetPos(b);
			D3DXVECTOR3 cpos = m_Fractal.GetPos(c);
			D3DXVECTOR3 dpos = m_Fractal.GetPos(d);

			D3DXVECTOR3 ab = bpos - apos;
			D3DXVECTOR3 ac = cpos - apos;
			D3DXVECTOR3 ad = dpos - apos;
			D3DXVECTOR3 abcNorm; 
			D3DXVec3Cross(&abcNorm,&ac,&ab);
			D3DXVECTOR3 acdNorm;
			D3DXVec3Cross(&acdNorm,&ad,&ac);
			D3DXVec3Normalize(&abcNorm,&abcNorm);
			D3DXVec3Normalize(&acdNorm,&acdNorm);
			m_vTriNorms[TriCount++]=abcNorm;
			m_vTriNorms[TriCount++]=acdNorm;

			vIndex++;
			
		}
		vIndex++;
		
	}
	m_pMesh->UnlockIndexBuffer();


	//vertex buffer stuff
	
	TheD3DApp::Instance()->GetD3DDev()->CreateVertexBuffer(m_NumVerts*sizeof(POSNORMUVVERTEX),D3DUSAGE_WRITEONLY,0,D3DPOOL_MANAGED,&m_Vb,0);
	POSNORMUVVERTEX* pVertices;
	V(m_pMesh->LockVertexBuffer(0, (void**)&pVertices));
	int curVert=0;

	//start at bottom left
	D3DXVECTOR3 curPos=minBounds;
	for(unsigned int z=0;z<numVertsZ;++z)
	{
		curPos.x = minBounds.x;
		for(unsigned int x=0;x<numVertsX;++x)
		{
			pVertices[curVert].Pos.x = curPos.x;
			pVertices[curVert].Pos.y = m_Fractal.GetPoint(curVert);
			pVertices[curVert].Pos.z = curPos.z;


			//Normal stuff 
			int NormVert=curVert-z;
			int T1 = NormVert*2;
			int T2 = T1+1;
			int T3 = T1-2; 

			int T6 = T2-(numCellsWide*2);
			int T5=T6-3;
			int T4=T6-2;
			D3DXVECTOR3 mean =D3DXVECTOR3(0,0,0);
			int contributers=0;
			
			//add together all the triangle normals
			//1 & 2, y<max && X<max
			if(curPos.z<maxBounds.z && curPos.x<maxBounds.x)
			{
				mean += m_vTriNorms[T1];
				mean += m_vTriNorms[T2];
				contributers +=2;
			}
			if(curPos.z<maxBounds.z && curPos.x>minBounds.x)
			{
				mean += m_vTriNorms[T3];
				contributers++;
			}
			if(curPos.z>minBounds.z && curPos.x>minBounds.x)
			{
				mean += m_vTriNorms[T4];
				mean += m_vTriNorms[T5];
				contributers +=2;
			}
			if(curPos.z>minBounds.z && curPos.x<maxBounds.x)
			{
				mean += m_vTriNorms[T6];
				contributers++;
			}


			//calculate mean
			mean /= contributers;
			D3DXVECTOR3 VertNorm;
			D3DXVec3Normalize(&VertNorm,&mean);

			//set vert normal
			pVertices[curVert].Norm = VertNorm;


			//UV Stuff
			if(m_bTile)
			{
				pVertices[curVert].UV = D3DXVECTOR2(x,z);
			}
			else
			{
				pVertices[curVert].UV = D3DXVECTOR2(x/numVertsX,z/numVertsZ);
			}

			//move right
			curPos.x += stepX;
			curVert++;
		}
		//move up
		curPos.z += stepZ;
	}
	
	m_pMesh->UnlockVertexBuffer();

	/*
	//POSVERTEX* pVB;
	 V(m_pMesh->LockVertexBuffer(0, (void**)&m_Vb));

	// Copy the vertices into the buffer
	memcpy(m_Vb, pVertices, sizeof(pVertices) );
	// Unlock the vertex buffer
	m_pMesh->UnlockVertexBuffer();
	*/
	// Prepare to copy the indices into the index buffer
	//VOID* IndexPtr;
	// Lock the index buffer
	V(m_pMesh->LockIndexBuffer( 0, (void**)&m_Ib ));

	// Copy the indices into the buffer
	memcpy(m_Ib, IndexData, sizeof(m_Ib) );
	// Unlock the buffer
	m_pMesh->UnlockIndexBuffer();
	
	V(D3DXComputeNormals(m_pMesh,0));

}
void Terrain::Create()
{
	if ((m_vPos!= NULL) && (m_Size != NULL) && (m_CellSize != NULL))
	{
		Create(m_Size,m_CellSize,m_vPos);
	}
}
int Terrain::GetCellSize()
{
	return m_CellSize;
}
Fractal* Terrain::GetFractal()
{
	return &m_Fractal;
}


float Terrain::GetHeightAtPoint(D3DXVECTOR2 point)
{
	Graph g = m_Fractal.GetGraph();
	//find 3 nearest points the graph
	int corners[3] = {-1,-1,-1};
	D3DXVECTOR3 cornersPos[3];
	float nearest = m_CellSize*m_Size;
	float nearest2 = m_CellSize*m_Size;
	float nearest3 = m_CellSize*m_Size;
	for(unsigned int i=0;i<g.GetNumNodes();++i)
	{
		D3DXVECTOR2 pos = g.GetNode(i)->GetPos()*m_CellSize;
		float length =D3DXVec2Length(&(pos-point));
		if(length<nearest)
		{
			nearest3=nearest2;
			corners[2]=corners[1];
			nearest2=nearest;
			corners[1]=corners[0];

			nearest=length;
			corners[0]=i;
		}
		else if(length<nearest2)
		{
			nearest3=nearest2;
			corners[2]=corners[1];

			nearest2=length;
			corners[1]=i;
		}
		else if(length<nearest3)
		{
			nearest3=length;
			corners[2]=i;
		}
	}

	if(nearest<0.001)
	{
		return m_Fractal.GetPoint(corners[0]);
	}

	D3DXVECTOR2 p =g.GetNode(corners[0])->GetPos()*m_CellSize;
	cornersPos[0]=D3DXVECTOR3(p.x,m_Fractal.GetPoint(corners[0]),p.y);

	p =g.GetNode(corners[1])->GetPos()*m_CellSize;
	cornersPos[1]=D3DXVECTOR3(p.x,m_Fractal.GetPoint(corners[1]),p.y);

	p =g.GetNode(corners[2])->GetPos()*m_CellSize;
	cornersPos[2]=D3DXVECTOR3(p.x,m_Fractal.GetPoint(corners[2]),p.y);


	D3DXVECTOR3 AB = cornersPos[1]-cornersPos[0];
	D3DXVECTOR3 AC = cornersPos[2]-cornersPos[0];
	D3DXVECTOR3 n;
	D3DXVec3Cross(&n,&AB,&AC);
	D3DXVec3Normalize(&n,&n);
	float A,B,C,D;
	A= n.x;
	B= n.y;
	C= n.z;

	float x= cornersPos[0].x;
	float y= cornersPos[0].y;
	float z= cornersPos[0].z;

	D = (A*x)+(B*y)+(C*z);

	float Y = D - ((A*point.x)+(C*point.y));
	float res = Y/B;
	return res;
}